using System;
using JigLibX.Physics;
using Microsoft.Xna.Framework.Graphics;
using Utilities;

namespace Microsoft.Xna.Framework
{
    static public class Utilities
    {
        static public Vector3 random_vector(this Random rand)
        {
            Func<float> r = () => (float)(rand.NextDouble() * 2.0 - 1.0);
            return Vector3.Normalize(new Vector3(r(), r(), r()));
        }

        static public BoundingSphere bounding_sphere(this Body body)
        {
            if (body.CollisionSkin == null)
                return new BoundingSphere(Vector3.Zero, 0.0f);

            BoundingSphere bs = new BoundingSphere(Vector3.Zero, 0.0f);
            for (int i = 0; i < body.CollisionSkin.NumPrimitives; i++)
            {
                var m = body.CollisionSkin.GetPrimitiveLocal(i);
                bs = BoundingSphere.CreateMerged(bs, new BoundingSphere(m.Position, m.Radius));
            }
            return bs;

        }

        static public Ray GetRay(this Viewport viewport, Vector2 touch, Matrix view, Matrix projection)
        {
            var near_touch = viewport.Unproject(new Vector3(touch.X, touch.Y, 0.0f), projection, view, Matrix.Identity);
            var far_touch = viewport.Unproject(new Vector3(touch.X, touch.Y, 1.0f), projection, view, Matrix.Identity);
            var touch_dir = Vector3.Normalize(far_touch - near_touch);
            return new Microsoft.Xna.Framework.Ray(near_touch, touch_dir);
        }

        static public GraphicsDevice GraphicsDevice(this Model model)
        {
            return model.Meshes[0].Effects[0].GraphicsDevice;
        }

        static public void DrawAsBody(this Model model, Body body, Matrix view, Matrix projection, Color color)
        {
            Vector3 p = body.Transform.Position;
            Matrix h = body.Transform.Orientation;
            var s = 1.0f;

            var world = model.World(p, h, s);

            //model.Draw(world, view, projection);
            DrawModel(model, world, view, projection, color);
        }
        static public void DrawAsBody(this Model model, Body body, Matrix view, Matrix projection)
        {
            Vector3 p = body.Transform.Position;
            Matrix h = body.Transform.Orientation;
            var s = 1.0f;

            var world = model.World(p, h, s);

            //model.Draw(world, view, projection);
            DrawModel(model, world, view, projection);
        }
        static public void DrawAsBody(this Model model, Body body, Matrix view, Matrix projection, float scale)
        {
            Vector3 p = body.Transform.Position;
            Matrix h = body.Transform.Orientation;
            //var s = 1.0f;

            var world = model.World(p, h, scale);

            //model.Draw(world, view, projection);
            DrawModel(model, world, view, projection);
        }

        static public void DrawAsBody(this Model model, Body body, Matrix view, Matrix projection, Vector3 scale)
        {
            Vector3 p = body.Transform.Position;
            Matrix h = body.Transform.Orientation;
            //var s = 1.0f;


            var world = model.World(p, h, scale);

            //model.Draw(world, view, projection);
            DrawModel(model, world, view, projection);
        }


        static public void DrawAsBody(this Model model, Matrix world_mul, Body body, Matrix view, Matrix projection)
        {
            Vector3 p = body.Transform.Position;
            Matrix h = body.Transform.Orientation;
            var s = 1.0f;

            var world = model.World(p, world_mul, s);

            //model.Draw(world, view, projection);
            DrawModel(model, world, view, projection);
        }

        static public void DrawAsBody(this Model model, Matrix world_mul, Body body, Matrix view, Matrix projection, Color color)
        {
            Vector3 p = body.Transform.Position;
            Matrix h = body.Transform.Orientation;
            var s = 1.0f;

            var world = model.World(p, world_mul, s);

            //model.Draw(world, view, projection);
            DrawModel(model, world, view, projection, color);
        }

        static public void Draw(this Model model, Vector3 p, Matrix h, float s, Matrix view, Matrix projection)
        {
            var world = model.World(p, h, s);

            //model.Draw(world, view, projection);
            DrawModel(model, world, view, projection);
        }

        static public void Draw(this Model model, Vector3 p, Matrix h, float s, Color color, Matrix view, Matrix projection)
        {
            //for_each (var mesh in model.Meshes)
            for(int i=0;i<model.Meshes.Count;i++)
            {
                var mesh = model.Meshes[i];
                //for_each (BasicEffect fx in mesh.Effects)
                for (int y = 0; y < mesh.Effects.Count; y++)
                    //fx.DiffuseColor = color.ToVector3();
                    ((BasicEffect)mesh.Effects[y]).DiffuseColor = color.ToVector3();
            }
            model.Draw(p, h, s, view, projection);
        }

        static public Matrix World(this Model model, Vector3 p, Matrix h, float s)
        {
            return model.World(p, h, Vector3.One * s);
        }

        static public Matrix World(this Model model, Vector3 p, Matrix h, Vector3 s)
        {
            var world = Matrix.CreateScale(s) * (h) * Matrix.CreateTranslation(p);
            if (model.Tag == null)
            {
                //var bs = model.Meshes.Select(m => m.BoundingSphere).Aggregate((a, c) => BoundingSphere.CreateMerged(a, c));


                BoundingSphere bs = new BoundingSphere();
                for (int i = 0; i < model.Meshes.Count; i++)
                {
                    var m = model.Meshes[i];
                    if (i == 0)
                    {
                        bs = m.BoundingSphere;
                    }
                    else
                    {
                        bs = BoundingSphere.CreateMerged(bs, m.BoundingSphere);
                    }
                }

                world = Matrix.CreateTranslation(-bs.Center) * Matrix.CreateScale(1.0f / bs.Radius) * world;
            }
            return world;
        }

        static public void EnableShipLighting(this Model model)
        {

            //for_each (var m in model.Meshes)
            for (int i = 0; i < model.Meshes.Count; i++)
            {
                var mesh = model.Meshes[i];
                //for_each (BasicEffect fx in mesh.Effects)
                for (int y = 0; y < mesh.Effects.Count; y++)
                {
                    BasicEffect fx = (BasicEffect)mesh.Effects[y];
                    fx.EnableDefaultLighting();
                    fx.PreferPerPixelLighting = false;
                }
            }
        }

        static public void MoveTowards(this Body self, Vector3 destination, float dt, float speed)
        {
            var towards_h = self.RotationTowards(destination);
            var heading = Quaternion.CreateFromRotationMatrix(self.Transform.Orientation);
            var heading1 = Quaternion.Slerp(heading, towards_h, dt);
            self.Orientation = Matrix.CreateFromQuaternion(heading1);
            self.Force = self.Orientation.Forward * self.Mass * speed;
            self.UpdateVelocity(dt);
        }

        static public Quaternion RotationTowards(this Body self, Vector3 destination)
        {
            var p1 = self.Position;
            var p2 = destination;
            var d1 = Vector3.Forward;
            var v12 = Vector3.Normalize(p2 - p1);
            var angle = (float)System.Math.Acos(Vector3.Dot(d1, v12));
            var axis = Vector3.Normalize(Vector3.Cross(d1, v12));
            return Quaternion.CreateFromAxisAngle(axis, angle);
        }

        static ArrayList<Body> empty_bodies = new ArrayList<Body>();
        static public void RotateAround(this Body self, Body other, float dt, float speed, float min_dist, float max_dist, ArrayList<Body> obstacles)
        {
            var dist = Vector3.Distance(self.Position, other.Position);
            if (obstacles.Count > 0)
            {
                var closest_obstacle = obstacles[0];
                var min_d = Vector3.Distance(self.Position, closest_obstacle.Position);
                if (min_d < dist)
                {
                    for (int i = 0; i < obstacles.Count; i++)
                    {
                        var o = obstacles[i];
                        var d = Vector3.Distance(self.Position, o.Position);
                        if (d < min_d)
                        {
                            min_d = d;
                            closest_obstacle = o;
                        }
                    }
                    self.RotateAround(closest_obstacle, dt, speed, min_dist, max_dist, empty_bodies);
                    return;
                }
            }

            var move_towards = MathHelper.SmoothStep(0.0f, 1.0f, (dist - min_dist) / (max_dist - min_dist));
            var move_away = 1.0f - move_towards;

            var towards_h = self.RotationTowards(other.Position);
            var away_h = other.RotationTowards(self.Position);
            var heading = Quaternion.CreateFromRotationMatrix(self.Transform.Orientation);
            var heading1 = Quaternion.Slerp(heading, towards_h, move_towards * dt);
            var heading2 = Quaternion.Slerp(heading1, away_h, move_away * dt);
            self.Orientation = Matrix.CreateFromQuaternion(heading2);
            self.Force = self.Orientation.Forward * self.Mass * speed;
            self.UpdateVelocity(dt);
        }

        static public Point ToPoint(this Vector2 v, Matrix screen_transform)
        {
            return new Point((int)(v.X / screen_transform.M11), (int)(v.Y / screen_transform.M22));
        }

        public static void CleanupComponents(this Game game)
        {
            for (int i = 0; i < game.Components.Count; i++)
            {
                ((GameComponent)game.Components[i]).Dispose();
                i--;
            }
            game.Components.Clear();
        }


        public static readonly float DEFAULT_SCREEN_WIDTH = 480.0f;
        public static readonly float DEFAULT_SCREEN_HEIGHT = 800.0f;

        public static Matrix ScreenTransformMatrix(this Game game, GraphicsDeviceManager graphics)
        {
            //return Matrix.CreateScale(game.GraphicsDevice.Viewport.Width / DEFAULT_SCREEN_WIDTH, game.GraphicsDevice.Viewport.Height / DEFAULT_SCREEN_HEIGHT, 1.0f);
            //if (game.Window.CurrentOrientation == DisplayOrientation.Portrait || game.Window.CurrentOrientation==DisplayOrientation.Default)
            if(graphics.SupportedOrientations == DisplayOrientation.Portrait || graphics.SupportedOrientations==DisplayOrientation.Default)
                //if (ScreenPosition.Position == ScreenPositions.Portrait)
                return Matrix.CreateScale(game.GraphicsDevice.Viewport.Width / 480, game.GraphicsDevice.Viewport.Height / 800, 1.0f);
            else
                return Matrix.CreateScale(game.GraphicsDevice.Viewport.Width / 800, game.GraphicsDevice.Viewport.Height / 480, 1.0f);
        }


        private static ArrayList<Matrix[]> bonesMatrixs = new ArrayList<Matrix[]>()
        {
            new Matrix[1], new Matrix[2], new Matrix[3], new Matrix[4], new Matrix[5],
            new Matrix[6], new Matrix[7], new Matrix[8], new Matrix[9], new Matrix[10]
        };
        public static void DrawModel(this Model model, Matrix world, Matrix view, Matrix projection)
        {            

            //model.Root.Transform = world;     
            //var _boneTransforms = bonesMatrixs[model.Bones.Count-1];
            //model.CopyAbsoluteBoneTransformsTo(_boneTransforms);     
            for (int i = 0; i < model.Meshes.Count; i++)
            {
                ModelMesh mesh = model.Meshes[i];
                for (int y = 0; y < mesh.Effects.Count; y++)
                {
                    BasicEffect be = (BasicEffect)mesh.Effects[y];
                    be.Projection = projection;
                    be.View = view;
                    be.World = mesh.ParentBone.Transform * world;
                }
                mesh.Draw();
            }           
            
        }

        public static void DrawModel(this Model model, Matrix world, Matrix view, Matrix projection, Color color)
        {

            //model.Root.Transform = world;     
            //var _boneTransforms = bonesMatrixs[model.Bones.Count-1];
            //model.CopyAbsoluteBoneTransformsTo(_boneTransforms);     
            for (int i = 0; i < model.Meshes.Count; i++)
            {
                ModelMesh mesh = model.Meshes[i];
                for (int y = 0; y < mesh.Effects.Count; y++)
                {
                    BasicEffect be = (BasicEffect)mesh.Effects[y];
                    //be.DiffuseColor = color.ToVector3();
                    be.EnableDefaultLighting();
                    be.Projection = projection;
                    be.View = view;
                    be.World = mesh.ParentBone.Transform * world;
                }
                mesh.Draw();
            }

        }

        public static ArrayList<int> cifre = new ArrayList<int>(10);
        public static string[] interi = new string[] { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9","-" };
        public static string void_string = "";
 



        ///<summary>
        ///Disegna una stringa numerica senza allocare nuovi oggetti stringa.
        /// </summary>
        /// <param name="init_string">Stringa da disegnare prima del numero, onsideranta nell'accentramento</param>
        /// <param name="center">Flag per indicare se usare la position come centro o come angolo in alto a sinistra della stringa</param>
        static public void DrawNumericString(this SpriteBatch self, SpriteFont spriteFont, string init_string, float num, Vector2 position, Color color, Boolean center)
        {
            //var num = (int)numf;
            if (init_string == null)
                init_string = void_string;
            cifre.Clear();
            var width = spriteFont.MeasureString(init_string).X;
            var height = spriteFont.MeasureString(interi[0]).Y;
            var neg = num < 0;
            num = neg ? -num : num;
            do
            {
                var s = (int)(num % 10);
                num = num / 10;
                cifre.Add(s);
                width += spriteFont.MeasureString(interi[s]).X;
            }
            while (((int)num) > 0);
            if (neg) cifre.Add(10);

            var drawPoint = position + new Vector2(center?width/2:width, center?-height/2:0);
            for (int i = 0; i < cifre.Count; i++)
            {
                drawPoint -= new Vector2(spriteFont.MeasureString(interi[cifre[i]]).X /*+ spriteFont.LineSpacing*/, 0);
                self.DrawString(spriteFont, interi[cifre[i]], drawPoint, color);                
            }
            drawPoint -= new Vector2(spriteFont.MeasureString(init_string).X /*+ spriteFont.LineSpacing*/, 0);
            self.DrawString(spriteFont, init_string, drawPoint, color); 
        }
    }
}